
P.PlotUtils = {};

P.PlotUtils.distance = function(pnt1, pnt2){
    return Math.sqrt(Math.pow((pnt1[0] - pnt2[0]), 2) + Math.pow((pnt1[1] - pnt2[1]), 2));
};

P.PlotUtils.wholeDistance = function(points){
    var distance = 0;
    for(var i=0; i<points.length-1; i++)
    distance += P.PlotUtils.distance(points[i], points[i+1]);
    return distance;
};

P.PlotUtils.getBaseLength = function(points){
    return Math.pow(P.PlotUtils.wholeDistance(points), 0.99);
    //return P.PlotUtils.wholeDistance(points);
};

P.PlotUtils.mid = function(pnt1, pnt2){
    return [(pnt1[0]+pnt2[0])/2, (pnt1[1]+pnt2[1])/2];
};

P.PlotUtils.getCircleCenterOfThreePoints = function(pnt1, pnt2, pnt3){
    var pntA = [(pnt1[0]+pnt2[0])/2, (pnt1[1]+pnt2[1])/2];
    var pntB = [pntA[0]-pnt1[1]+pnt2[1], pntA[1]+pnt1[0]-pnt2[0]];
    var pntC = [(pnt1[0]+pnt3[0])/2, (pnt1[1]+pnt3[1])/2];
    var pntD = [pntC[0]-pnt1[1]+pnt3[1], pntC[1]+pnt1[0]-pnt3[0]];
    return P.PlotUtils.getIntersectPoint(pntA, pntB, pntC, pntD);
};

P.PlotUtils.getIntersectPoint = function(pntA, pntB, pntC, pntD){
    if(pntA[1] == pntB[1]){
        var f = (pntD[0]-pntC[0])/(pntD[1]-pntC[1]);
        var x = f*(pntA[1]-pntC[1])+pntC[0];
        var y = pntA[1];
        return [x, y];
    }
    if(pntC[1] == pntD[1]){
        var e = (pntB[0]-pntA[0])/(pntB[1]-pntA[1]);
        x = e*(pntC[1]-pntA[1])+pntA[0];
        y = pntC[1];
        return [x, y];
    }
    e = (pntB[0]-pntA[0])/(pntB[1]-pntA[1]);
    f = (pntD[0]-pntC[0])/(pntD[1]-pntC[1]);
    y = (e*pntA[1]-pntA[0]-f*pntC[1]+pntC[0])/(e-f);
    x = e*y-e*pntA[1]+pntA[0];
    return [x, y];
};

P.PlotUtils.getAzimuth = function(startPnt, endPnt){
    var azimuth;
    var angle=Math.asin(Math.abs(endPnt[1] - startPnt[1]) / P.PlotUtils.distance(startPnt, endPnt));
    if (endPnt[1] >= startPnt[1] && endPnt[0] >= startPnt[0])
        azimuth=angle + Math.PI;
    else if (endPnt[1] >= startPnt[1] && endPnt[0] < startPnt[0])
        azimuth=P.Constants.TWO_PI - angle;
    else if (endPnt[1] < startPnt[1] && endPnt[0] < startPnt[0])
        azimuth=angle;
    else if (endPnt[1] < startPnt[1] && endPnt[0] >= startPnt[0])
        azimuth=Math.PI - angle;
    return azimuth;
};

P.PlotUtils.getAngleOfThreePoints = function(pntA, pntB, pntC){
    var angle=P.PlotUtils.getAzimuth(pntB, pntA) - P.PlotUtils.getAzimuth(pntB, pntC);
    return (angle<0 ? angle + P.Constants.TWO_PI : angle);
};

P.PlotUtils.isClockWise = function(pnt1, pnt2, pnt3){
    return ((pnt3[1]-pnt1[1])*(pnt2[0]-pnt1[0]) > (pnt2[1]-pnt1[1])*(pnt3[0]-pnt1[0]));
};

P.PlotUtils.getPointOnLine = function(t, startPnt, endPnt){
    var x = startPnt[0] + (t * (endPnt[0] - startPnt[0]));
    var y = startPnt[1] + (t * (endPnt[1] - startPnt[1]));
    return [x, y];
};

P.PlotUtils.getCubicValue = function(t, startPnt, cPnt1, cPnt2, endPnt){
    t = Math.max(Math.min(t, 1), 0);
    var tp = 1 - t;
    var t2 = t * t;
    var t3 = t2 * t;
    var tp2 = tp * tp;
    var tp3 = tp2 * tp;
    var x = (tp3*startPnt[0]) + (3*tp2*t*cPnt1[0]) + (3*tp*t2*cPnt2[0]) + (t3*endPnt[0]);
    var y = (tp3*startPnt[1]) + (3*tp2*t*cPnt1[1]) + (3*tp*t2*cPnt2[1]) + (t3*endPnt[1]);
    return [x, y];
};

P.PlotUtils.getThirdPoint = function(startPnt, endPnt, angle, distance, clockWise){
    var azimuth=P.PlotUtils.getAzimuth(startPnt, endPnt);
    var alpha = clockWise ? azimuth+angle : azimuth-angle;
    var dx=distance * Math.cos(alpha);
    var dy=distance * Math.sin(alpha);
    return [endPnt[0] + dx, endPnt[1] + dy];
};

P.PlotUtils.getArcPoints = function(center, radius, startAngle, endAngle){
    var x, y, pnts=[];
    var angleDiff = endAngle - startAngle;
    angleDiff = angleDiff < 0 ? angleDiff + P.Constants.TWO_PI : angleDiff;
    for (var i=0; i<=P.Constants.FITTING_COUNT; i++)
    {
        var angle = startAngle + angleDiff * i / P.Constants.FITTING_COUNT;
        x=center[0] + radius * Math.cos(angle);
        y=center[1] + radius * Math.sin(angle);
        pnts.push([x, y]);
    }
    return pnts;
};

P.PlotUtils.getBisectorNormals = function(t, pnt1, pnt2, pnt3){
    var normal = P.PlotUtils.getNormal(pnt1, pnt2, pnt3);
    var dist = Math.sqrt(normal[0]*normal[0] + normal[1]*normal[1]);
    var uX = normal[0]/dist;
    var uY = normal[1]/dist;
    var d1 = P.PlotUtils.distance(pnt1, pnt2);
    var d2 = P.PlotUtils.distance(pnt2, pnt3);
    if(dist > P.Constants.ZERO_TOLERANCE){
        if(P.PlotUtils.isClockWise(pnt1, pnt2, pnt3)){
            var dt = t * d1;
            var x = pnt2[0] - dt*uY;
            var y = pnt2[1] + dt*uX;
            var bisectorNormalRight = [x, y];
            dt = t * d2;
            x = pnt2[0] + dt*uY;
            y = pnt2[1] - dt*uX;
            var bisectorNormalLeft = [x, y];
        }
        else{
            dt = t * d1;
            x = pnt2[0] + dt*uY;
            y = pnt2[1] - dt*uX;
            bisectorNormalRight = [x, y];
            dt = t * d2;
            x = pnt2[0] - dt*uY;
            y = pnt2[1] + dt*uX;
            bisectorNormalLeft = [x, y];
        }
    }
    else{
        x = pnt2[0] + t*(pnt1[0] - pnt2[0]);
        y = pnt2[1] + t*(pnt1[1] - pnt2[1]);
        bisectorNormalRight = [x, y];
        x = pnt2[0] + t*(pnt3[0] - pnt2[0]);
        y = pnt2[1] + t*(pnt3[1] - pnt2[1]);
        bisectorNormalLeft = [x, y];
    }
    return [bisectorNormalRight, bisectorNormalLeft];
};

P.PlotUtils.getNormal = function(pnt1, pnt2, pnt3){
    var dX1 = pnt1[0] - pnt2[0];
    var dY1 = pnt1[1] - pnt2[1];
    var d1 = Math.sqrt(dX1*dX1 + dY1*dY1);
    dX1 /= d1;
    dY1 /= d1;

    var dX2 = pnt3[0] - pnt2[0];
    var dY2 = pnt3[1] - pnt2[1];
    var d2 = Math.sqrt(dX2*dX2 + dY2*dY2);
    dX2 /= d2;
    dY2 /= d2;

    var uX = dX1 + dX2;
    var uY = dY1 + dY2;
    return [uX, uY];
};

P.PlotUtils.getCurvePoints = function(t, controlPoints){
    var leftControl = P.PlotUtils.getLeftMostControlPoint(controlPoints);
    var normals = [leftControl];
    for(var i=0; i<controlPoints.length-2; i++){
        var pnt1 = controlPoints[i];
        var pnt2 = controlPoints[i+1];
        var pnt3 = controlPoints[i+2];
        var normalPoints = P.PlotUtils.getBisectorNormals(t, pnt1, pnt2, pnt3);
        normals = normals.concat(normalPoints);
    }
    var rightControl = P.PlotUtils.getRightMostControlPoint(controlPoints);
    normals.push(rightControl);
    var points = [];
    for(i=0; i<controlPoints.length-1; i++){
        pnt1 = controlPoints[i];
        pnt2 = controlPoints[i+1];
        points.push(pnt1);
        for(var t=0; t<P.Constants.FITTING_COUNT; t++){
            var pnt = P.PlotUtils.getCubicValue(t/P.Constants.FITTING_COUNT, pnt1, normals[i*2], normals[i*2+1], pnt2);
            points.push(pnt);
        }
        points.push(pnt2);
    }
    return points;
};

P.PlotUtils.getLeftMostControlPoint = function(controlPoints){
    var pnt1 = controlPoints[0];
    var pnt2 = controlPoints[1];
    var pnt3 = controlPoints[2];
    var pnts = P.PlotUtils.getBisectorNormals(0, pnt1, pnt2, pnt3);
    var normalRight = pnts[0];
    var normal = P.PlotUtils.getNormal(pnt1, pnt2, pnt3);
    var dist = Math.sqrt(normal[0]*normal[0] + normal[1]*normal[1]);
    if(dist > P.Constants.ZERO_TOLERANCE){
        var mid = P.PlotUtils.mid(pnt1, pnt2);
        var pX = pnt1[0] - mid[0];
        var pY = pnt1[1] - mid[1];

        var d1 = P.PlotUtils.distance(pnt1, pnt2);
        // normal at midpoint
        var n  = 2.0/d1;
        var nX = -n*pY;
        var nY = n*pX;

        // upper triangle of symmetric transform matrix
        var a11 = nX*nX - nY*nY
        var a12 = 2*nX*nY;
        var a22 = nY*nY - nX*nX;

        var dX = normalRight[0] - mid[0];
        var dY = normalRight[1] - mid[1];

        // coordinates of reflected vector
        var controlX = mid[0] + a11*dX + a12*dY;
        var controlY = mid[1] + a12*dX + a22*dY;
    }
    else{
        controlX = pnt1[0] + t*(pnt2[0] - pnt1[0]);
        controlY = pnt1[1] + t*(pnt2[1] - pnt1[1]);
    }
    return [controlX, controlY];
};

P.PlotUtils.getRightMostControlPoint = function(controlPoints){
    var count = controlPoints.length;
    var pnt1 = controlPoints[count-3];
    var pnt2 = controlPoints[count-2];
    var pnt3 = controlPoints[count-1];
    var pnts = P.PlotUtils.getBisectorNormals(0, pnt1, pnt2, pnt3);
    var normalLeft = pnts[1];
    var normal = P.PlotUtils.getNormal(pnt1, pnt2, pnt3);
    var dist = Math.sqrt(normal[0]*normal[0] + normal[1]*normal[1]);
    if(dist > P.Constants.ZERO_TOLERANCE){
        var mid = P.PlotUtils.mid(pnt2, pnt3);
        var pX = pnt3[0] - mid[0];
        var pY = pnt3[1] - mid[1];

        var d1 = P.PlotUtils.distance(pnt2, pnt3);
        // normal at midpoint
        var n  = 2.0/d1;
        var nX = -n*pY;
        var nY = n*pX;

        // upper triangle of symmetric transform matrix
        var a11 = nX*nX - nY*nY
        var a12 = 2*nX*nY;
        var a22 = nY*nY - nX*nX;

        var dX = normalLeft[0] - mid[0];
        var dY = normalLeft[1] - mid[1];

        // coordinates of reflected vector
        var controlX = mid[0] + a11*dX + a12*dY;
        var controlY = mid[1] + a12*dX + a22*dY;
    }
    else{
        controlX = pnt3[0] + t*(pnt2[0] - pnt3[0]);
        controlY = pnt3[1] + t*(pnt2[1] - pnt3[1]);
    }
    return [controlX, controlY];
};

P.PlotUtils.getBezierPoints = function(points){
    if (points.length <= 2)
        return points;

    var bezierPoints=[];
    var n=points.length - 1;
    for (var t=0; t <= 1; t+=0.01){
        var x=y=0;
        for (var index=0; index <= n; index++){
            var factor=P.PlotUtils.getBinomialFactor(n, index);
            var a=Math.pow(t, index);
            var b=Math.pow((1 - t), (n - index));
            x+=factor * a * b * points[index][0];
            y+=factor * a * b * points[index][1];
        }
        bezierPoints.push([x, y]);
    }
    bezierPoints.push(points[n]);
    return bezierPoints;
};

P.PlotUtils.getBinomialFactor = function(n, index){
    return P.PlotUtils.getFactorial(n) / (P.PlotUtils.getFactorial(index) * P.PlotUtils.getFactorial(n - index));
};

P.PlotUtils.getFactorial = function(n){
    if (n <= 1)
        return 1;
    if (n == 2)
        return 2;
    if (n == 3)
        return 6;
    if (n == 4)
        return 24;
    if (n == 5)
        return 120;
    var result=1;
    for (var i=1; i <= n; i++)
        result*=i;
    return result;
};

P.PlotUtils.getQBSplinePoints = function(points){
    if (points.length <= 2 )
        return points;

    var n = 2;

    var bSplinePoints=[];
    var m=points.length - n - 1;
    bSplinePoints.push(points[0]);
    for (var i=0; i <= m; i++){
        for (var t=0; t <= 1; t+=0.05){
            var x=y=0;
            for (var k=0; k <= n; k++){
                var factor=P.PlotUtils.getQuadricBSplineFactor(k, t);
                x+=factor * points[i + k][0];
                y+=factor * points[i + k][1];
            }
            bSplinePoints.push([x, y]);
        }
    }
    bSplinePoints.push(points[points.length - 1]);
    return bSplinePoints;
};

P.PlotUtils.getQuadricBSplineFactor = function(k, t){
    if (k == 0)
        return Math.pow(t - 1, 2) / 2;
    if (k == 1)
        return (-2 * Math.pow(t, 2) + 2 * t + 1) / 2;
    if (k == 2)
        return Math.pow(t, 2) / 2;
    return 0;
};